//
// Created by fangp on 3/10/18.
//

#include <memory.h>
#include "sysmatcher.h"
#include "scanner.h"
#include "parser.h"

#define MACRO_DEF_MACRO       "defmacro"

void *get_macro_function(String name) {
    void *macro;
    hashmap_get(globalFunny->macros, name, &macro);
    return macro;
}

String macro_function(String functionName, int argc, Statement **argv) {
    MacroFunc0 func0;
    MacroFunc1 func1;
    MacroFunc2 func2;
    MacroFunc3 func3;
    MacroFunc4 func4;

    switch(argc) {
        case 0:
            func0 = (MacroFunc0)get_macro_function(functionName);
            return func0();
        case 1:
            func1 = (MacroFunc1)get_macro_function(functionName);
            return func1(*argv);
        case 2:
            func2 = (MacroFunc2)get_macro_function(functionName);
            return func2(*argv, *(argv + 1));
        case 3:
            func3 = (MacroFunc3)get_macro_function(functionName);
            return func3(*argv, *(argv + 1), *(argv + 2));
        case 4:
            func4 = (MacroFunc4)get_macro_function(functionName);
            return func4(*argv, *(argv + 1), *(argv + 3), *(argv + 4));
        default:
            return NULL;
    }
    return NULL;
}


#define ALLOC_STRING_LENGTH  50

static Statement *next_macro_statement(Queue *repQueue, String macro) {
    String str = alloc_string(ALLOC_STRING_LENGTH);
    Statement *statement = NULL;

    if (*macro == PAREN_L) {
        int i = 0;
        while (*macro != PAREN_R) {
            str[i++] = *macro++;
        }
        str[i++] = *macro;
        str[i] = '\0';

        statement = parse(str);
        return (Statement *)find_value(repQueue, get_arg_name(statement));
    }

    return NULL;
}

static int is_macro_char(char c) {
    return isascii(c) && isupper(c);
}

static String get_macro_word(char *p) {
    String buf = alloc_string(ALLOC_STRING_LENGTH);
    String r = buf;
    while (*p && is_macro_char(*p)) {
        *buf++ = *p++;
    }
    *buf = '\0';
    return r;
}

static String get_args(char *p, char *q) {
    if (p >= q) {
        return NULL;
    }
    String buf = alloc_string(ALLOC_STRING_LENGTH);
    String r = buf;
    while (p != q) {
        *buf++ = *p++;
    }
    *buf++ = *q;
    *buf = '\0';
    return r;
}

static String parse_macro(Queue *repQueue, String macro) {
    String buf = init_string();
    Statement **sp = (Statement **)sys_malloc(5 * sizeof(Statement *));
    Statement *s = NULL;
    int args = 0;
    String temp = NULL;
    char c;
    char *p;
    char *q;
    String argStr = NULL;

    while (*macro) {
        c = *macro;

        if (c == PAREN_L) {
            s = next_macro_statement(repQueue, macro);
            p = strchr(macro, PAREN_R);
            if (!p)
                return NULL;
            macro = p + 1;

            temp = to_string_statement(s);
            buf = dyna_strcat(buf, temp);
        } else if (is_macro_char(c)) {
            String macroWord = get_macro_word(macro);
            macro += strlen(macroWord);
            args = 0;

            p = strchr(macro, '[');
            if (!p)
                return NULL;
            macro = p + 1;

            q = strchr(macro, ']');
            if (!q)
                return NULL;
            macro = q + 1;

            if (p + 1 == q) {
                *sp = NULL;
            } else {
                argStr = get_args(p + 1, q - 1);
                s = next_macro_statement(repQueue, argStr);
                *sp = s;
                p = strchr(argStr, ',');
                args++;
                while (p != NULL) {
                    argStr = p + 1;
                    s = next_macro_statement(repQueue, argStr);
                    *(sp + args) = s;
                    p = strchr(argStr, ',');
                    args++;
                }
                *(sp + args) = NULL;
            }

            temp = macro_function(macroWord, args, sp);
            buf = dyna_strcat(buf, temp);
        } else if (c == '\\') {
            macro++;
            buf = append_char(buf, *macro);
            if (*macro)
                macro++;
        } else {
            buf = append_char(buf, *macro);
            if (*macro)
                macro++;
        }
    }

    return buf;
}

String eval_sys(Mapping *m, Queue *repQueue) {
    String name = copy_string(m->sys->name);
    String function = copy_string(m->sys->function);
    String mappingStr = copy_string(m->sys->mapping);
    String run = copy_string(m->sys->run);
    String eval = copy_string(m->sys->eval);
    Mapping *mapping = NULL;

    String* array = NULL;
    String mappingSource;
    String mappingTarget;

    if (!equals_string(mappingStr, NIL_DEF)) {
        mapping = alloc_mapping();
        mapping->sys = NULL;
        if (strcmp(name, MACRO_DEF_MACRO) == 0) {
            mapping->isMacro = TRUE;
        } else {
            mapping->isMacro = FALSE;
        }

        array = split_string_once(mappingStr, '>');
        mappingSource = array[0];
        mappingTarget = array[1];

        if (!equals_string(function, NIL_DEF)) {
            mapping->functionName = parse_macro(repQueue, function);
        }
        mapping->source = parse_macro(repQueue, trim_string(mappingSource));
        mapping->target = parse_macro(repQueue, trim_string(mappingTarget));
        mapping->sourceStatement = parse(mapping->source);
        mapping->targetStatement = parse(mapping->target);
        mapping->starter = to_dfa(mapping->sourceStatement);

        trie_add(globalFunny->mappingTrie, mapping);
    }

    if (!equals_string(run, NIL_DEF)) {
        /*
        if (*run == '#') { //special cases
            if (equals_string(run, MACRO_ADD_MAPPING_TO_CLASS)) {
                add_mapping_to_class(globalFunny->class, mapping);
            }
        } else {
            parse_macro(repQueue, run);
        }
        */
        parse_macro(repQueue, run);
    }

    if (!equals_string(eval, NIL_DEF)) {
        return parse_macro(repQueue, eval);
    } else {
        return "";
    }
}

Queue *get_mappings(Statement *s) {
    if (s == NULL)
        return NULL;
    return trie_find(globalFunny->mappingTrie, s);
}


